using System;
using System.Collections.Generic;
using System.Text;

namespace DataPaper
{
    class Border2
    {
        public readonly int nbBlock_r;
        public readonly int nbBlock_c;
        public readonly BW bw;
        public Center[,] block;

        public Border2(BW bw, int nbBlock_r, int nbBlock_c)
        {
            this.nbBlock_r = nbBlock_r;
            this.nbBlock_c = nbBlock_c;
            this.bw = bw;
            Point[] top = FindBorder(bw.top_left, nbBlock_c, 0, bw.first_u, bw.first_w, bw.first_b);
            Point[] left = FindBorder(bw.top_left, nbBlock_r, bw.first_u, 0, bw.first_w, bw.first_b);
            Point[] right = FindBorder(top[top.Length-1], nbBlock_r, bw.first_u, 0, bw.first_w, bw.first_b);
            Point[] bottom = FindBorder(left[left.Length - 1], nbBlock_c, 0, bw.first_u, bw.first_w, bw.first_b);
            block = FindCenters(top, bottom, right, left);
        }
        public void Info(int b_r, int b_c,
                        out Point p, out double u_r, out double u_c,
                        out double b, out double w,
                        out double bl)
        {
            Center center = block[b_r, b_c];
            w = center.white;
            b = center.black;
            bl = center.bl;
            if (b_r == 0)
                u_r = block[b_r + 1, b_c].center.r - center.center.r;
            else if (b_r == nbBlock_r - 1)
                u_r = center.center.r - block[b_r - 1, b_c].center.r;
            else
                u_r = 0.5 * (block[b_r + 1, b_c].center.r - block[b_r - 1, b_c].center.r);
            u_r /= 17.0;
            if (b_c == 0)
                u_c = block[b_r , b_c+1].center.c - center.center.c;
            else if (b_c == nbBlock_c - 1)
                u_c = center.center.c - block[b_r , b_c-1].center.c;
            else
                u_c = 0.5 * (block[b_r, b_c + 1].center.c - block[b_r, b_c - 1].center.c);
            u_c /= 17.0;
            p = new Point(center.center.r - u_r * (17 / 2),
                center.center.c - u_c * (17 / 2));
        }


        public struct Center
        {
            public Center(Point center,
            double white, double black, double bl)
            {
                this.center = center;
                this.white = white;
                this.black = black;
                this.bl = bl;
            }
            public readonly Point center;
            public readonly double white, black, bl;
        }

#region Find black white lb
        private void CheckSame(Point p1, Point p2, double u, string info)
        {
            if (Math.Abs(p1.c - p2.c) > u / 2 ||
                Math.Abs(p1.r - p2.r) > u / 2)
                throw new Exception("Mismatch of:"+info);
        }
        private Point AddVector(Point p, Point start, Point end)
        {
            return new Point(p.r + end.r - start.r, p.c + end.c - start.c);
        }

        private void FindBlackWhite(Point[] points, int br, out double black, out double white, out double lb)
        {
            black = 0;
            white = 0;
            lb = int.MaxValue;
            Point first = points[br * 17 + 1];
            Point last = points[br * 17 +17];
            double u = Math.Max(last.r-first.r, last.c-first.c)/(17-1);
            for (int i = br * 17 + 1; i < br * 17 + 1 + 17; ++i)
            {
                double b; double w;
                bw.CalibrateBW(points[i], out w, out b, u);
                black += b;
                white += w;                
            }
            black /= 17;
            white /= 17;
            for (int i = br * 17 + 1; i < br * 17 + 1 + 17; ++i)
            {
                int l = bw.LowBlackLevel(points[i].r, points[i].c, white, black, u);
                if (l < lb)
                    lb = l;
            }
        }
        private void FindBlackWhite(Point[] top, Point[] bottom, Point[] right,
            Point[] left, int br, int bc, out double black, out double white, out double lb)
        {
            black = 0;
            white = 0;
            lb = 0;
            double b; double w; double l;
            {
                List<Point[]> lst = new List<Point[]>();
                lst.Add(left);
                lst.Add(right);

                foreach (Point[] v in lst)
                {
                    FindBlackWhite(v, br, out b, out w, out l);
                    black += b;
                    white += w;
                    lb += l;
                }
            }
            {
                List<Point[]> lst = new List<Point[]>();
                lst.Add(top);
                lst.Add(bottom);
                foreach (Point[] v in lst)
                {
                    FindBlackWhite(v, bc, out b, out w, out l);
                    black += b;
                    white += w;
                    lb += l;
                }
            }
            black /= 4;
            white /= 4;
            lb /= 4;
        }
#endregion Find black white lb

        // This function is called for (r) for (c)
        private Center[,] FindCenters(Point[] top, Point[] bottom, Point[] right,
            Point[] left)
        {
            #region Check
             
            CheckSame(top[0], left[0], bw.first_u, "top left");
            CheckSame(top[top.Length - 1], right[0], bw.first_u, "top right");
            CheckSame(bottom[0], left[left.Length - 1], bw.first_u, "bottom left");
            CheckSame(bottom[bottom.Length - 1], right[right.Length - 1], bw.first_u, "bottom right");

            #endregion
            Center[,] block = new Center[nbBlock_r, nbBlock_c];
            for (int br = 0; br < nbBlock_r; ++br)
                for (int bc = 0; bc < nbBlock_c; ++bc)
                {
                    double white, black, bl;
                    FindBlackWhite(top, bottom, right, left, br, bc, out black, out white, out bl);
                    Point c;
                    double u;
                    if (br == 0)
                    {
                        u = (top[bc * 17 + 17 + 1].c - top[bc * 17 + 1].c) / 17.0;
                        //top[(i) * 17 + 17 / 2 + 1]
                        if (bc == 0)
                        {
                            c = AddVector(top[0], top[0], top[17 / 2 + 1]);
                            c = AddVector(c, left[0], left[17 / 2 + 1]);
                        }
                        else
                            c = AddVector(block[br, bc - 1].center, top[(bc - 1) * 17 + 17 / 2 + 1],
                                          top[(bc) * 17 + 17 / 2 + 1]);
                    }
                    else
                    {
                        if (bc == 0)
                        {
                            c = AddVector(block[br - 1, bc].center, left[(br - 1) * 17 + 17 / 2 + 1],
                                          left[(br) * 17 + 17 / 2 + 1]);
                            u = (left[br * 17 + 17 + 1].r - left[br * 17 + 1].r) / 17.0;
                        }
                        else
                        {
                            Point cv = AddVector(block[br - 1, bc].center, block[br - 1, bc - 1].center,
                                block[br, bc - 1].center);

                            Point ch = AddVector(block[br, bc - 1].center, block[br - 1, bc - 1].center,
                                block[br - 1, bc].center);
                            c = new Point(0.5 * (cv.r + ch.r), 0.5 * (cv.c + ch.c));
                            u = (c.r - block[br - 1, bc].center.r)/17.0;
                        }
                    }
                    bw.CalibrateXY(ref c, white, black, u);
                    block[br, bc] = new Center(c, white, black, bl);
                }
            return block;
        }

        // before: point just before the first block
        public Point[] FindBorder(Point before,
            int nbBlock, double u_r, double u_c,
            double init_w, double init_b)
        {
            Point p = before;
            Point[] res_p = new Point[nbBlock * 17 + 2];
            double init_u = Math.Max(u_r, u_c);
            res_p[0] = p;
            for (int i = 1; i < nbBlock * 17 + 2; ++i)
            {
                p = new Point(p.r + u_r, p.c + u_c);
                bw.CalibrateXY(ref p, init_w, init_b, init_u);
                res_p[i] = p;
            }
            return res_p;
        }

    }
}
